package com.lap.analysis.source.builder;

import com.lap.analysis.env.entity.Environment;
import com.lap.analysis.source.entity.ClassInfo;
import com.lap.analysis.source.entity.FieldInfo;
import com.lap.analysis.source.entity.MethodInfo;
import com.lap.analysis.tools.Constants;
import com.lap.analysis.tools.ProjectUtil;
import com.lap.analysis.tools.UriUtil;
import com.lap.framework.tools.Named;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.*;

import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * 源文件创建
 *
 * @author Shuisheng Lao(劳水生)
 * @version 0.0.1
 */
@Slf4j
public abstract class SourceBuilder {

  public abstract void builderClass(Environment env);

  public abstract List<ClassInfo> context();

  public static class Default extends SourceBuilder {

    private final List<ClassInfo> classInfoList = new ArrayList<>();

    @Override
    public void builderClass(Environment env) {
      env.getBasePackages()
          .forEach(
              basePackage -> {
                File parentPath =
                    new File(ProjectUtil.getJavaPath() + Named.periods2Path(basePackage));
                Optional.ofNullable(parentPath.listFiles())
                    .ifPresent(
                        files ->
                            Arrays.stream(files)
                                .forEach(file -> parserFile(env, basePackage, file)));
              });
    }

    private void parserFile(Environment env, String basePackage, File file) {
      if (isSourceFile(env, file)) {
        Optional.ofNullable(parserClass(basePackage, file))
            .ifPresent(
                clazz -> {
                  ClassInfo fileInfo = new ClassInfo();
                  fileInfo.setFileName(FilenameUtils.getBaseName(file.getName()));
                  fileInfo.setPackageName(basePackage);
                  fileInfo.setSupperClass(env.getSupperClass());
                  fileInfo.setRemark(getRemark(clazz));
                  fileInfo.setMethods(parserMethod(clazz));
                  classInfoList.add(fileInfo);
                });
      }
    }

    private boolean isSourceFile(Environment env, File file) {
      return FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("java")
          && (env.getFileConf().isAll()
              || env.getFileConf().getFileList().contains(file.getName()));
    }

    private Class<?> parserClass(String packageName, File file) {
      String className =
          StringUtils.join(packageName, ".", FilenameUtils.getBaseName(file.getName()));
      try {
        return Class.forName(className);
      } catch (ClassNotFoundException e) {
        log.error(e.getLocalizedMessage(), e);
        return null;
      }
    }

    private String getRemark(Class<?> clazz) {
      return Optional.ofNullable(clazz.getAnnotation(Tag.class))
          .map(tag -> StringUtils.defaultString(tag.name()))
          .orElse("");
    }

    private List<MethodInfo> parserMethod(Class<?> clazz) {
      String supUri = getParentUri(clazz);
      return Stream.of(clazz.getMethods())
          .sorted(Comparator.comparing(Method::getName).reversed())
          .map(method -> getMethod(method, supUri))
          .filter(Objects::nonNull)
          .toList();
    }

    private MethodInfo getMethod(Method method, String supUri) {
      Pair<String, String> pair = parserMethod(method);
      if (StringUtils.isNotBlank(pair.getRight())) {
        MethodInfo info = new MethodInfo();

        Operation operation = method.getAnnotation(Operation.class);
        if (Objects.nonNull(operation)) {
          String summary = operation.summary();
          info.setRemark(summary);
        }

        info.setUri(UriUtil.getUri(supUri, pair.getLeft()));
        info.setMethod(pair.getRight());
        info.setCsvPath(info.getUri());
        info.setName(method.getName());
        info.setFields(parserParam(method));
        info.setPage(isPage(method));
        info.setArgMap(isArgMap(info)); // 参数大于3算长参数
        info.setPathVariable(isPathVariable(pair.getLeft()));
        return info;
      }
      return null;
    }

    private boolean isArgMap(MethodInfo methodInfo) {
      return methodInfo.getFields().size() > 3;
    }

    private boolean isPathVariable(String uri) {
      return Pattern.compile("\\{(.*?)\\}", Pattern.DOTALL).matcher(uri).find();
    }

    private String getParentUri(Class<?> clazz) {
      return Optional.ofNullable(getUri(clazz)).orElseGet(() -> getUri(clazz.getSuperclass()));
    }

    private String getUri(Class<?> clazz) {
      return Optional.ofNullable(clazz.getAnnotation(RequestMapping.class))
          .filter(requestMapping -> ArrayUtils.isNotEmpty(requestMapping.value()))
          .map(requestMapping -> requestMapping.value()[0])
          .orElse(null);
    }

    private List<FieldInfo> parserParam(Method method) {
      return Stream.of(method.getParameters())
          .flatMap(
              parameter ->
                  isPrimitiveOrWrapper(parameter.getType())
                      ? Stream.of(new FieldInfo(parameter.getType(), parameter.getName()))
                      : Stream.of(parameter.getType().getDeclaredFields())
                          .map(field -> new FieldInfo(field.getType(), field.getName())))
          .toList();
    }

    private boolean isPage(Method method) {
      Parameter[] parameters = method.getParameters();
      for (Parameter parameter : parameters) {
        String simpleName =
            Optional.of(parameter.getType())
                .map(Class::getSuperclass)
                .map(Class::getSimpleName)
                .orElse(Constants.EMPTY);
        if (simpleName.equalsIgnoreCase("PageRequest")) {
          return true;
        }
      }
      return false;
    }

    private boolean isPrimitiveOrWrapper(Class<?> clazz) {
      return ClassUtils.isPrimitiveOrWrapper(clazz) || clazz == String.class;
    }

    private Pair<String, String> parserMethod(Method method) {
      PostMapping post = method.getAnnotation(PostMapping.class);
      if (Objects.nonNull(post)) {
        return Pair.of(UriUtil.getFirstUri(post.value()), "PostMapping");
      }

      GetMapping get = method.getAnnotation(GetMapping.class);
      if (Objects.nonNull(get)) {
        return Pair.of(UriUtil.getFirstUri(get.value()), "GetMapping");
      }

      PutMapping put = method.getAnnotation(PutMapping.class);
      if (Objects.nonNull(put)) {
        return Pair.of(UriUtil.getFirstUri(put.value()), "PutMapping");
      }

      DeleteMapping delete = method.getAnnotation(DeleteMapping.class);
      if (Objects.nonNull(delete)) {
        return Pair.of(UriUtil.getFirstUri(delete.value()), "DeleteMapping");
      }
      return Pair.of(null, null);
    }

    @Override
    public List<ClassInfo> context() {
      return classInfoList;
    }
  }
}
